package bufferpoll

import (
	"context"
	"distributed-cache/constant"
	"distributed-cache/superconfig"
	"fmt"
	"github.com/go-redis/redis/v8"
	"hash/crc32"
	"math/rand"
	"strconv"
	"strings"
	"time"
)

type void struct{}

type RedisProxy struct {
	real         *redis.Client
	next         *RedisProxy
	mappingTable []int
	status       int
}

type RedisPool struct {
	realRedis      *RedisProxy
	redisConfigSet map[string]void
	virtualNums    []int
	len            int
	cap            int
}

func generateRandomNumber(start int, end int, count int) []int {
	if end < start || (end-start) < count {
		return nil
	}
	nums := make([]int, 0)
	r := rand.New(rand.NewSource(time.Now().UnixNano()))
	for len(nums) < count {
		num := r.Intn(end-start) + start

		exist := false
		for _, v := range nums {
			if v == num {
				exist = true
				break
			}
		}
		if !exist {
			nums = append(nums, num)
		}
	}
	return nums
}

func (rp *RedisPool) Init(ctx context.Context, configRedisList superconfig.RedisConfigs) {
	virtualPool := GetVirtualPool(ctx)
	rp.redisConfigSet = make(map[string]void)
	var voidValue void
	virtualNums := generateRandomNumber(0, 1024, 1024)
	rp.virtualNums = virtualNums
	// 初始化所有redis实例，并按权重分配到各个虚拟节点上
	for _, confRedis := range configRedisList {
		rdb := redis.NewClient(&redis.Options{
			Addr:     confRedis.Addr,
			Password: confRedis.Password,
			DB:       confRedis.DB,
		})
		rp.redisConfigSet[strings.Join([]string{confRedis.Addr, confRedis.Password, strconv.Itoa(confRedis.DB)}, ":")] = voidValue
		rpx := rp.add(rdb)
		for i := 0; i < confRedis.Weight && virtualPool.GetCurrentIndex() < len(virtualNums); i++ {
			currentNodeIndex := virtualNums[virtualPool.GetCurrentIndex()]
			fmt.Printf("virtualIndex is: %d\n", currentNodeIndex)
			rpx.mappingTable = append(rpx.mappingTable, currentNodeIndex)
			virtualPool.OperationNode[currentNodeIndex].RedisBean = rpx
			rpx.status = constant.STABILIZATION
			virtualPool.IncreaseCurrentIndex()
		}
	}
}

func transferOldToNew(ctx context.Context, immigrant *RedisProxy, emigrant *RedisProxy, toMoveIndex int) {
	var keys []string
	var err error
	var cursor uint64
	for {
		keys, cursor, err = emigrant.real.Scan(ctx, cursor, strconv.Itoa(toMoveIndex)+":*", 20).Result()
		if err != nil {
			fmt.Println("transferOldToNew err happen")
			continue
		}
		// 该节点如果有此前缀的key，则迁移
		if len(keys) != 0 {
			fmt.Printf("key is %d\n", toMoveIndex)
			values, _ := emigrant.real.MGet(ctx, keys...).Result()
			keyToValue := make([]interface{}, 0)
			for i := 0; i < len(keys); i++ {
				if values[i] == nil {
					continue
				}
				keyToValue = append(keyToValue, keys[i])
				keyToValue = append(keyToValue, values[i])
			}
			fmt.Printf("transferOldToNew immigrant.real.MSet len %d\n", len(keyToValue))
			_, err = immigrant.real.MSet(ctx, keyToValue...).Result()
			if err != nil {
				fmt.Println("transferOldToNew immigrant.real.MSet err ")
			}
			emigrant.real.Del(ctx, keys...)
		}

		if cursor == 0 {
			break
		}
	}
}

func (rp *RedisPool) addAndAdjust(ctx context.Context, redisConfig *superconfig.RedisConfig) {
	virtualNums := rp.virtualNums
	rdb := redis.NewClient(&redis.Options{
		Addr:     redisConfig.Addr,
		Password: redisConfig.Password,
		DB:       redisConfig.DB,
	})
	// 将redis实例加入到rp中并返回该实例的代表
	rpx := rp.add(rdb)
	/**
	节点列表中至少会存在一个实例，所以
	如果前面有一个实例，后面也有一个实例，则从前面的实例开始到该实例之间的节点，都要从这个实例后面的第一个实例中迁移出来
	如果前面没有实例，则表示从左边第一个节点到该实例的节点，和节点列表最后一个实例到节点列表的终点，都需要迁移
	如果后面没有实例，则表示从前面的实例到该实例的位置，都需要从节点列表开始的第一个实例迁移出来
	*/
	for i := 0; i < redisConfig.Weight && virtualPool.GetCurrentIndex() < len(virtualNums); i++ {
		// 虚拟节点的下标
		currentNodeIndex := virtualNums[virtualPool.GetCurrentIndex()]
		fmt.Printf("db is %d and virtualIndex is: %d\n", redisConfig.DB, currentNodeIndex)
		rpx.mappingTable = append(rpx.mappingTable, currentNodeIndex)
		virtualPool.OperationNode[currentNodeIndex].RedisBean = rpx
		// 找到当前节点后面的第一个redis实例，可能在后面也可能在前面，因为是循环
		nextPoint := currentNodeIndex + 1
		// 需要找到起始位置，从该位置到本位置内的所有数据，都要从nextPoint代表的实例中迁移出来
		prePoint := currentNodeIndex - 1
		for ; nextPoint < len(virtualPool.OperationNode) && virtualPool.OperationNode[nextPoint].RedisBean == nil; nextPoint++ {
		}
		for ; prePoint >= 0 && virtualPool.OperationNode[prePoint].RedisBean == nil; prePoint-- {
		}
		// 第一个实例后的第一个空节点
		prePoint++
		if prePoint > 0 && nextPoint < len(virtualPool.OperationNode) {
			// 左右两边都有实例
			nextRedisBean := virtualPool.OperationNode[nextPoint].RedisBean
			for ; prePoint <= currentNodeIndex; prePoint++ {
				transferOldToNew(ctx, rpx, nextRedisBean, prePoint)
			}
		} else if prePoint <= 0 {
			//左边没有实例
			prePoint = 0
			emigrationRedis := virtualPool.OperationNode[nextPoint].RedisBean

			for ; prePoint <= currentNodeIndex; prePoint++ {
				transferOldToNew(ctx, rpx, emigrationRedis, prePoint)
			}

			// 从右往左找到第一个实例后的第一个节点的位置
			lastRedisIndex := len(virtualPool.OperationNode) - 1
			for ; virtualPool.OperationNode[lastRedisIndex].RedisBean == nil; lastRedisIndex-- {
			}
			lastEmptyNode := lastRedisIndex + 1
			for ; lastEmptyNode < len(virtualPool.OperationNode); lastEmptyNode++ {
				transferOldToNew(ctx, rpx, emigrationRedis, lastEmptyNode)
			}
		} else {
			//右边没有实例
			//找到左边第一个实例，作为迁出数据的节点
			firstRedisIndex := 0
			for ; firstRedisIndex < len(virtualPool.OperationNode) && virtualPool.OperationNode[firstRedisIndex].RedisBean == nil; firstRedisIndex++ {
			}
			for ; prePoint <= currentNodeIndex; prePoint++ {
				transferOldToNew(ctx, rpx, virtualPool.OperationNode[firstRedisIndex].RedisBean, prePoint)
			}
		}

		rpx.status = constant.STABILIZATION
		virtualPool.IncreaseCurrentIndex()

	}
}

func (rp *RedisPool) AddNewRedisList(ctx context.Context, configRedisList superconfig.RedisConfigs) {
	toAddSet := make(map[string]void)
	// 求差集
	var currentKey string
	for _, confRedis := range configRedisList {
		currentKey = strings.Join([]string{confRedis.Addr, confRedis.Password, strconv.Itoa(confRedis.DB)}, ":")
		if _, ok := rp.redisConfigSet[currentKey]; ok {
			continue
		}
		toAddSet[currentKey] = void{}
		rp.addAndAdjust(ctx, &confRedis)
	}
}

func (rp *RedisPool) add(rs *redis.Client) *RedisProxy {
	// 头插法
	rpx := &RedisProxy{real: rs, status: constant.MIGRATION}
	if rp.realRedis == nil {
		rp.realRedis = rpx
		return rpx
	}
	rpx.next = rp.realRedis
	rp.realRedis = rpx
	return rpx
}

func selectIndex(key string) int {
	// todo 通过hash key来返回一个下标
	crc32q := crc32.MakeTable(0xD5828281)
	index := int(crc32.Checksum([]byte(key), crc32q))
	return index & (1023)
}

func (rp *RedisPool) SetValue(ctx context.Context, key string, value string, expiration time.Duration) {

}

func (rp *RedisPool) GetValue(ctx context.Context, key string) {

}
